#include "vga.h"

#include "io.h"

#define VGA_BASE 0xB8000      // vga 显存起始地址
#define VGA_SCREEN_WIDTH 80   // vga 屏幕宽度（可容纳字符数）
#define VGA_SCREEN_HEIGHT 25  // vga 屏幕高度

#define CURSOR_LINE_REG 0xE      // 行号寄存器
#define CURSOR_COL_REG 0xF       // 列号寄存器
#define CURSOR_INDEX_PORT 0x3D4  // 光标行列索引端口号
#define CURSOR_DATA_PORT 0x3D5   // 光标数据端口号

/* ========= 以下函数仅供参考，可以根据自己的需求进行修改，甚至删除 ========= */

/* 将光标设定到特定位置
 * 提示：使用 outb */
void set_cursor_pos(unsigned short int pos) {
    /* finished */
    if (pos >= (VGA_SCREEN_HEIGHT * VGA_SCREEN_WIDTH)) return;
    //向0xE输出高位
    outb(CURSOR_INDEX_PORT, CURSOR_LINE_REG);
    outb(CURSOR_DATA_PORT, (unsigned char)((pos & 0x0000FF00) >> 8));
    //向0xF输出低位
    outb(CURSOR_INDEX_PORT, CURSOR_COL_REG);
    outb(CURSOR_DATA_PORT, (unsigned char)(pos & 0x000000FF));
    return;
}

/* 获取光标当前所在位置
 * 提示：使用 inb */
unsigned short int get_cursor_pos(void) {
    /* finished */
    unsigned char high, low;
    //向0xE获取高位
    outb(CURSOR_INDEX_PORT, CURSOR_LINE_REG);
    high = inb(CURSOR_DATA_PORT);
    //向0xF获取高位
    outb(CURSOR_INDEX_PORT, CURSOR_COL_REG);
    low = inb(CURSOR_DATA_PORT);
    unsigned short int val = (high << 8) | low;
    return val;
}

/* 滚屏，vga 屏幕满时使用。丢弃第一行内容，将剩余行整体向上滚动一行
 * 提示：使用指针修改显存 */
void scroll_screen(void) {
    /* finished */
    unsigned short* p;
    p = (unsigned short*)VGA_BASE;
    //上移一行
    for (int i = 1; i < VGA_SCREEN_HEIGHT; i++) {
        for (int j = 0; j < VGA_SCREEN_WIDTH; j++) {
            p[(i - 1) * VGA_SCREEN_WIDTH + j] = p[i * VGA_SCREEN_WIDTH + j];
        }
    }
    //最后一行清空
    for (int i = 0; i < VGA_SCREEN_WIDTH; i++) {
        p[(VGA_SCREEN_HEIGHT - 1) * VGA_SCREEN_WIDTH + i] =
            ((p[(VGA_SCREEN_HEIGHT - 1) * VGA_SCREEN_WIDTH + i]) & 0xFF00) |
            (' ' & 0x00FF);
    }
    //鼠标位置返回最后一行首部
    set_cursor_pos((VGA_SCREEN_HEIGHT - 1) * VGA_SCREEN_WIDTH);
    return;
}

/* 向 vga 的特定光标位置 pos 输出一个字符
 * 提示：使用指针修改显存 */
void put_char2pos(unsigned char c, int color, unsigned short int pos) {
    /* finished */
    if (pos >= (VGA_SCREEN_WIDTH * VGA_SCREEN_HEIGHT)) return;  //超出限度则丢弃
    unsigned short int nextpos = pos + 1;
    if (c == '\n') {  //回车直接切换至下一行
        if ((nextpos % VGA_SCREEN_WIDTH) != 0) {
            nextpos = (nextpos / VGA_SCREEN_WIDTH + 1) * VGA_SCREEN_WIDTH;
        }
    } else {  //否则直接修改显存
        unsigned short* p = (unsigned short*)VGA_BASE + pos;
        *p = (unsigned short)(((color & 0x000000FF) << 8) | c);
    }
    if (nextpos >= VGA_SCREEN_HEIGHT * VGA_SCREEN_WIDTH) {  //屏幕满则换行
        scroll_screen();
        nextpos = nextpos - VGA_SCREEN_WIDTH;
    }
    set_cursor_pos(nextpos);
    return;
}

/* ========= 以下函数接口禁止修改 ========= */

/* 清除屏幕上所有字符，并将光标位置重置到顶格
 * 提示：使用指针修改显存 */
void clear_screen(void) {
    /* finished */
    unsigned short int w = ' ';
    unsigned short* p;
    p = (unsigned short*)VGA_BASE;
    for (unsigned short int i = 0; i < VGA_SCREEN_HEIGHT * VGA_SCREEN_WIDTH;
         i++) {
        p[i] = w;
    }
    set_cursor_pos(0);
    return;
}

/* 向 vga 的当前光标位置输出一个字符串，并移动光标位置到串末尾字符的下一位
 * 如果超出了屏幕范围，则需要滚屏
 * 需要能够处理转义字符 \n */
void append2screen(char* str, int color) {
    /* finished */
    unsigned int cursor = get_cursor_pos();
    for (int i = 0; str[i] != 0; i++) {
        unsigned int cursor = get_cursor_pos();
        put_char2pos(str[i], color, cursor);
    }
    return;
}